
import random
import numpy as np
import pandas as pd
from typing import List, Dict

import tqdm
from agent import Firms, HouseHolds, Government, Banks, CentralBank
from contract import DepositContract
from contract import LoanContractCollection
from contract import LoanContract
from contract import BondContract
from contract import JobContract
from contract import CapitalContract
from contract import StockHolderContract
from contract import ConsumptionContract
from trade import trade_from_reserve_to_deposit, trade_from_reserve_to_reserve
from utils import ParaPro, TransactionFlowItem, EpisodeItem, INI_YEAR
from utils import MarketInfo
p = ParaPro()


class CreditMarkets:
    def __init__(
        self,
        year: int,  # 开始年份
        c_firms: List[Firms],  # 消费品公司Agent集合
        k_firms: List[Firms],  # 资本品公司Agent集合
        banks: List[Banks],  # 银行Agent集合
        households: List[HouseHolds],  # 居民部门Agent集合
        government: Government,  # 政府Agent
        central_bank: CentralBank,  # 中央银行Agent
        ):

        self.year = year
        self.households: List[HouseHolds] = households

        self.c_firms: List[Firms] = c_firms
        self.c_firms_map = {c_firm.firm_id: c_firm for c_firm in self.c_firms}

        self.k_firms: List[Firms] = k_firms
        self.k_firms_map = {k_firm.firm_id: k_firm for k_firm in self.k_firms}

        self.firms_map: Dict[str, Firms] = {}
        self.firms_map = {**self.c_firms_map, **self.k_firms_map}

        self.banks: List[Banks] = banks
        self.government: Government = government
        self.central_bank: CentralBank = central_bank

        self.bonds_interest_rate = p.ir.interest_rate_of_bonds  # 初始化债券利率

        self.banks_id = [bank.bank_id for bank in self.banks]  # 银行id列表
        self.banks_map = {b.bank_id: b for b in self.banks}

        self.households_id = [h.household_id for h in self.households]  # 居民id列表
        self.households_map = {h.household_id: h for h in self.households}

        self.ini_firm_loan_contract()  # 初始化公司贷款合同
        self.ini_household_deposit_contract()  # 初始化居民存款合同
        self.ini_firm_deposit_contract()  # 初始化公司存款合同
        self.ini_stockholder_of_firm()  # 初始化股东分配
        self.ini_government_bond_contract()  # 初始化债券合约

        gov_workers_id = self.ini_c_k_firm_job_contract()  # 初始化c，k公司就业关系
        self.ini_gov_and_unemployed_household_job_contract(gov_workers_id)  # 初始化政府雇员和失业人员关系

        self.ini_c_k_goods_contract()
        self.ini_household_consumption_contract()
        self.tf_item = TransactionFlowItem()
        self.ep_item = EpisodeItem()
        self.ch_log = []
        self.new_contract_log = []
        self.yearly_info = {INI_YEAR:MarketInfo(INI_YEAR)}

    # 贷款合同与贷款关系的生成
    def ini_firm_loan_contract(self):
        print(f">>>> 正在初始化公司贷款合同，包括【bank_id】银行编号-【firm_id】公司编号-【amount】贷款金额"
              f"-【interest_rate】贷款利率-【interest_per_year】贷款利息,\n"
              f">>>> 从公司视角类的属性增加了贷款合约.deposits_contract，从银行视角类的属性也同样增加了贷款合约.deposits_contracts"
              )
        loan_args = {
            'k': p.n.number_of_k_firms / p.n.number_of_banks,
            'c': p.n.number_of_c_firms / p.n.number_of_banks
            }
        for ck_key, firms in {'k': self.k_firms, 'c': self.c_firms}.items():
            for i_f, firm in enumerate(firms):
                b_i = int(i_f // loan_args[ck_key])
                bank = self.banks_map[f"bank_{b_i}"]
                contracts_collection = []
                for i, loan_amount in enumerate(firm.payment_of_loans_list):
                    loan_contract = {
                        'amount': loan_amount,
                        'interest_rate': p.ir.interest_rate_of_loans,
                        'firm_id': firm.firm_id,
                        'contract_year': 1992 - i,
                        'left_year': p.para.duration_or_loan - i,
                        'bank_id': bank.bank_id
                    }
                    contracts_collection.append(LoanContract(loan_contract))
                firm.batch_of_loan_contracts = LoanContractCollection(contracts_collection)
        for firms in [self.c_firms, self.k_firms]:
            for firm in firms:
                for loan_contract in firm.batch_of_loan_contracts:
                    bank = self.search_bank_by_id(loan_contract.bank_id)
                    loan_contracts = bank.loans_contracts.setdefault(firm.firm_id, LoanContractCollection([]))
                    loan_contracts.add_new_loan(loan_contract)
        # 这里设置为不同的容器是因为未来一个公司的batch和一个银行的batch不相同

    def ini_household_deposit_contract(self):
        """
        初始化居民和银行的存款合同，合同内容包括bank_id,household_id,amount,interest_rate,interest_per_year,text
        :return:
        """
        print(f">>>> 正在初始化居民存款合同，包括【bank_id】银行编号-【household_id】居民编号-【amount】存款金额"
              f"-【interest_rate】存款利率-【interest_per_year】存款利息\n"
              f">>>> 从居民视角类的属性增加了存款合约.deposits_contract，从银行视角类的属性也同样增加了存款合于.deposits_contracts")
        h_deposits = p.h.deposits_of_households / p.w.number_of_households
        households_for_each_bank = p.w.number_of_households / p.n.number_of_banks
        for h_i, h in enumerate(self.households):
            b_i = int(h_i // households_for_each_bank)
            bank_id = f"bank_{b_i}"
            bank = self.search_bank_by_id(bank_id)
            deposit_contract = {
                'bank_id': bank_id,
                'depositor': h.household_id,
                'amount': h_deposits,
                'interest_rate': bank.deposit_interest_rate,
            }
            bank_household_deposit_contract = DepositContract(deposit_contract)
            bank.deposits_contracts[h.household_id] = bank_household_deposit_contract
            h.deposits_contracts = bank_household_deposit_contract

    def ini_firm_deposit_contract(self):
        """
        初始化公司存款合同，合同内容包括bank_id,firm_id,amount,interest_rate,interest_per_year,text
        :return:
        """
        print(f">>>> 正在初始化公司存款合同，包括【bank_id】银行编号-【firm_id】公司编号-【amount】存款金额"
              f"-【interest_rate】存款利率-【interest_per_year】存款利息,\n"
              f">>>> 从公司视角类的属性增加了存款合约.deposits_contract，从银行视角类的属性也同样增加了存款合于.deposits_contracts")
        deposits_args = {'k': p.k.deposits_capital_firms_holds / p.n.number_of_k_firms,
                         'c': p.c.deposits_consumption_firm_holds / p.n.number_of_c_firms}

        c_firms_for_each_bank = p.n.number_of_c_firms / p.n.number_of_banks
        for i_f, c_firm in enumerate(self.c_firms):
            b_i = int(i_f // c_firms_for_each_bank)
            bank_id = f"bank_{b_i}"
            bank = self.search_bank_by_id(bank_id)
            deposit_contract = {
                'bank_id': bank_id,
                'depositor': c_firm.firm_id,
                'amount': deposits_args['c'],
                'interest_rate': bank.deposit_interest_rate,
            }
            bank_c_firm_deposit = DepositContract(deposit_contract)
            bank.deposits_contracts[c_firm.firm_id] = bank_c_firm_deposit
            c_firm.deposits_contracts = bank_c_firm_deposit

        k_firms_for_each_bank = p.n.number_of_k_firms / p.n.number_of_banks
        for i_k, k_firm in enumerate(self.k_firms):
            b_i = int(i_k // k_firms_for_each_bank)
            bank_id = f"bank_{b_i}"
            bank = self.search_bank_by_id(bank_id)
            deposit_contract = {
                'bank_id': bank_id,
                'depositor': k_firm.firm_id,
                'amount': deposits_args['k'],
                'interest_rate': bank.deposit_interest_rate,
            }
            bank_k_firm_deposits = DepositContract(deposit_contract)
            bank.deposits_contracts[k_firm.firm_id] = bank_k_firm_deposits
            k_firm.deposits_contracts = bank_k_firm_deposits

    def ini_stockholder_of_firm(self):
        """
        初始化
        """
        print(">>> 初始化 股东归属关系(具体为每个公司它的股权属于哪个居民")

        average_bank_stock_holder_per_bank = p.w.number_of_households / p.n.number_of_banks  # 平均一个银行多少股东
        for i_h, h in enumerate(self.households):
            i_b = int(i_h // average_bank_stock_holder_per_bank)
            b = self.banks[i_b]
            stockholder_contract = StockHolderContract({
                'stock_id': b.bank_id,
                'stockholder_id': h.household_id,
                'share': 1 / average_bank_stock_holder_per_bank,
                'obj_type': 'bank'
                })
            h.stockholder_contract[b.bank_id] = stockholder_contract
            b.stockholder_contract[h.household_id] = stockholder_contract

        average_c_firm_stock_holder_per_firm = p.w.number_of_households / p.n.number_of_c_firms  # 平均一个c公司多少股东
        for i_h, h in enumerate(self.households):
            i_c = int(i_h // average_c_firm_stock_holder_per_firm)
            c = self.c_firms[i_c]
            stockholder_contract = StockHolderContract({'stock_id': c.firm_id, 'stockholder_id': h.household_id,
                                                        'share': 1 / average_c_firm_stock_holder_per_firm,
                                                        'obj_type': 'c_firm'})
            h.stockholder_contract[c.firm_id] = stockholder_contract
            c.stockholder_contract[h.household_id] = stockholder_contract

        average_k_firm_stock_holder_per_firm = p.w.number_of_households / p.n.number_of_k_firms  # 平均一个k公司多少股东
        for i_h, h in enumerate(self.households):
            i_k = int(i_h // average_k_firm_stock_holder_per_firm)
            k = self.k_firms[i_k]
            stockholder_contract = StockHolderContract({
                'stock_id': k.firm_id,
                'stockholder_id': h.household_id,
                'share': 1 / average_k_firm_stock_holder_per_firm,
                'obj_type': 'k_firm'})
            h.stockholder_contract[k.firm_id] = stockholder_contract
            k.stockholder_contract[h.household_id] = stockholder_contract

    def ini_government_bond_contract(self):
        """
        初始化政府债券合同，持有者分别为中央银行和商业银行，
        合同内容包括bank_id,amount,interest_rate,issue_year,total_year,remaining_year，
        单个银行持有的初始化债券金额为：INI_BANK_BONDS，利率为INI_INTEREST_RARE_OF_BOND
        :return:
        """
        print(f'>>>> 初始化政府债券,政府负债{self.government.bonds:.2f},其中'
              f'银行持有{self.banks[0].bonds * len(self.banks):.2f},中央银行持有{self.central_bank.bonds:.2f}')

        bank_contracts_counts = 0
        for bank in self.banks:
            average_amount = bank.bonds / p.para.duration_or_loan  # 总债券除以期数
            for i in range(int(p.para.duration_or_loan)):
                b_contract = {'amount': average_amount,
                              'interest_rate': p.ir.interest_rate_of_bonds,
                              'issue_year': self.year - i,
                              'remaining_year': p.para.duration_or_loan - i}
                bank_bonds_contracts = BondContract(b_contract)
                bank.bonds_contracts.append(bank_bonds_contracts)
                self.government.bonds_collection.append(bank_bonds_contracts)
                bank_contracts_counts += 1
            print('   >', f"一共{len(self.banks)}银行, 每家银行持有国债{bank.bonds:.2f}, 分成{p.para.duration_or_loan}期"
                      f", {bank_contracts_counts}份合约")
        # 把中银行初始化持有的债券，传递给政府
        for bond_contract in self.central_bank.bonds_collection:
            self.government.bonds_collection.append(bond_contract)

    def ini_c_k_goods_contract(self):
        """
        初始化c公司与k公司匹配关系，k公司资本品卖给c公司
        """
        print('>>> 初始化公司C与K的匹配关系')
        firms_per_k_firm = int(p.n.number_of_c_firms / p.n.number_of_k_firms)
        for i_c, c_firm in enumerate(self.c_firms):

            k_firm = self.k_firms[i_c // firms_per_k_firm]
            k_firm.produce_plan_pricing_and_cal_need_workers()

            investment = c_firm.investment()

            contract = {
                'c_firm_id': c_firm.firm_id,
                'k_firm_id': k_firm.firm_id,
                'price': k_firm.price_and_recruit.price,
                'quantity': investment
                }

            c_k_goods_contract = CapitalContract(contract)
            c_firm.capital_goods_contracts_collection[k_firm.firm_id] = c_k_goods_contract
            k_firm.capital_goods_contracts_collection[c_firm.firm_id] = c_k_goods_contract

    def ini_c_k_firm_job_contract(self):
        # 先初始化所有的K公司员工
        average_workers_in_k_firm = int(p.w.number_of_capital_firm_workers / p.n.number_of_k_firms)
        k_firm_workers_id = random.sample(self.households_id, int(p.w.number_of_capital_firm_workers))
        for h_i, worker_id in enumerate(k_firm_workers_id):
            k_i = h_i // average_workers_in_k_firm
            k_firm = self.k_firms_map[f"firm_k_{k_i}"]
            h = self.search_household_by_id(worker_id)
            jb_c = JobContract(
                {'wage': p.h.households_wage, 'dole': p.h.households_wage * p.ratio.ratio_of_dole_to_wage,
                 'household_id': worker_id, 'employer': f"firm_k_{k_i}",
                 'employed': [True, True, True, True]})
            h.job_contract = jb_c
            k_firm.job_contracts_collection[worker_id] = jb_c
        left_households_id = [id_ for id_ in self.households_id if id_ not in k_firm_workers_id]
        # 再初始化所有的C公司员工
        average_workers_in_c_firm = int(p.w.number_of_consumption_firm_workers / p.n.number_of_c_firms)
        c_firm_workers_id = random.sample(left_households_id, int(p.w.number_of_consumption_firm_workers))
        for h_i, worker_id in enumerate(c_firm_workers_id):
            c_i = h_i // average_workers_in_c_firm
            c_firm = self.c_firms_map[f"firm_c_{c_i}"]
            h = self.search_household_by_id(worker_id)
            jb_c = JobContract(
                {'wage': p.h.households_wage,
                 'dole': p.h.households_wage * p.ratio.ratio_of_dole_to_wage,
                 'household_id': worker_id, 'employer': f"firm_c_{c_i}",
                 'employed': [True, True, True, True]})
            h.job_contract = jb_c
            c_firm.job_contracts_collection[worker_id] = jb_c
        left_households_id_after_ini_firms = [id_ for id_ in left_households_id if id_ not in c_firm_workers_id]
        return left_households_id_after_ini_firms

    def ini_gov_and_unemployed_household_job_contract(self, households_id_after_firm):
        households_id_for_gov = random.sample(households_id_after_firm, int(p.w.number_of_government_workers))
        for worker_id in households_id_for_gov:
            h = self.search_household_by_id(worker_id)
            jb_c = JobContract(
                {'wage': p.h.households_wage, 'dole': p.h.households_wage * p.ratio.ratio_of_dole_to_wage,
                 'household_id': worker_id, 'employer': f"gov", 'employed': [True, True, True, True]})
            h.job_contract = jb_c
            self.government.job_contracts_collection[worker_id] = jb_c
        unemployed_household_id = [id_ for id_ in households_id_after_firm if id_ not in households_id_for_gov]
        for worker_id in unemployed_household_id:
            h = self.search_household_by_id(worker_id)
            jb_c = JobContract(
                {'wage': p.h.households_wage, 'dole': p.h.households_wage * p.ratio.ratio_of_dole_to_wage,
                 'household_id': worker_id, 'employer': f"gov", 'employed': [False, False, False, False]})
            h.job_contract = jb_c
            self.government.job_contracts_collection[worker_id] = jb_c
        return unemployed_household_id

    def ini_household_consumption_contract(self):
        """
        初始化居民与C公司匹配关系，c公司消费品卖给H居民
        """
        print('>>> 初始化居民与消费品公司的匹配关系')
        households_per_c_firm = int(p.w.number_of_households / p.n.number_of_c_firms)
        price_of_c_firms = {}
        for c in self.c_firms:
            c.produce_plan_pricing_and_cal_need_workers()
            price_of_c_firms[c.firm_id] = c.price_and_recruit.price

        for i_h, h in enumerate(self.households):
            c_i = i_h // households_per_c_firm
            c_firm_id = f"firm_c_{c_i}"
            price = price_of_c_firms[c_firm_id]
            c_firm = self.c_firms_map[c_firm_id]
            a1, a2, max_q, q = h.consume(price)
            contract = {
                'c_firm_id': c_firm.firm_id,
                'household_id': h.household_id,
                'price': price,
                'quantity': q,
                'con_deposit': h.deposits_contracts.amount,
                'con_income': h.job_contract.wage if h.job_contract.employed else h.job_contract.dole,
                'a1':a1,
                'a2':a2,
                'max_q':max_q,
                'q':q,
                }
            household_consumption_contract = ConsumptionContract(contract)
            h.consumption_contract = household_consumption_contract
            c_firm.consumption_goods_contracts_collection[h.household_id] = household_consumption_contract

    def search_firm_by_id(self, firm_id) -> Firms:
        """
        通过id找到公司的实体
        :param firm_id:
        :return:
        """
        return self.firms_map[firm_id]

    def search_bank_by_id(self, bank_id) -> Banks:
        """
        通过id找到银行的实体
        :param bank_id:
        :return:
        """
        for bank in self.banks:
            if bank.bank_id == bank_id:
                return bank
        # 若未找到匹配的银行，抛出异常
        raise ValueError(f"未找到 ID 为 {bank_id} 的银行")

    def search_household_by_id(self, household_id) -> HouseHolds:
        """
        通过id找到居民的实体
        :param household_id:
        :return:
        """
        return self.households_map[household_id]

    def reset_state(self):
        self.yearly_info[self.year] = MarketInfo(self.year)
        self.update_doles()

    def update_doles(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'doles'):
            wages_average = np.average([h.job_contract.wage for h in self.households if h.job_contract.employed[-1]])
            info.wages_average = wages_average
            info.doles = wages_average * p.ratio.ratio_of_dole_to_wage
        return self.yearly_info[self.year].doles

    def firm_adjust_employee(self):
        minus_employee = []
        add_minus_record = {}
        for firms in [self.c_firms, self.k_firms]:
            for f in firms:
                f.yearly_info[f.year].workers_start = len(f.job_contracts_collection)
                workers_need = f.price_and_recruit.worker_needed
                workers_in_job_contract = len(f.job_contracts_collection)
                # 补充新工人
                if workers_need > workers_in_job_contract:
                    # 需要从失业市场招人
                    gap_n = workers_need - workers_in_job_contract
                    add_minus_record[f"{f.id_()}"] = gap_n
                    for i in range(gap_n):
                        # 随机选出一个失业人员
                        # household_id_for_select = list(self.government.job_contracts_collection.keys())
                        household_id_for_select= self.government.list_unemployed_id_of_households()
                        if len(household_id_for_select) > 0:
                            h_i = random.choice(household_id_for_select)
                            hire_h = self.households_map[h_i]
                            # 失业人员调整工资预期
                            un_rate = self.cal_unemployment_rate()
                            ask_wages = hire_h.update_wages_contract(un_rate)
                            employed = hire_h.job_contract.employed
                            new_employed = employed[1:]
                            new_employed.append(True)
                            contract = {'wage': ask_wages,
                                        'dole': self.yearly_info[f.year].doles,
                                        'household_id': hire_h.household_id, 
                                        'employer': f.firm_id,
                                        'employed': new_employed
                                        }
                            # 双方绑定新的就业合同
                            new_job_contraction = JobContract(contract)
                            hire_h.job_contract = new_job_contraction
                            f.job_contracts_collection[hire_h.household_id] = new_job_contraction
                            # 政府的dole合同失效
                            del self.government.job_contracts_collection[hire_h.household_id]
                            # print('change 增加就业', contract)
                        else:
                            f.record_gap() # 记录下缺口，
                # 裁员
                elif workers_need < workers_in_job_contract:
                    gap_n = workers_in_job_contract - workers_need
                    add_minus_record[f"{f.id_()}"] = - gap_n
                    minus_employee.append(f"{f.id_()}准备减少{gap_n}员工")
                    for i in range(gap_n):
                        if len(f.job_contracts_collection) > 1:
                            h_id = random.choice(list(f.job_contracts_collection.keys()))
                            h_unemployed = self.search_household_by_id(h_id)
                            old_job_contract = h_unemployed.job_contract
                            employed = h_unemployed.job_contract.employed
                            new_employed = employed[1:]
                            new_employed.append(False)
                            contract = {'wage': old_job_contract.wage,
                                        'dole': self.yearly_info[f.year].doles,
                                        'household_id': h_unemployed.household_id,
                                        'employer': self.government.id_(),
                                        'employed': new_employed}
                            # print('change 减少就业', contract)
                            new_contract = JobContract(contract)
                            h_unemployed.job_contract = new_contract
                            del f.job_contracts_collection[h_id]
                            self.government.job_contracts_collection[h_id] = new_contract

                f.yearly_info[f.year].workers_end = len(f.job_contracts_collection)

    def firm_employee_turnover(self):
        for f_id, f in self.firms_map.items():
            n = int(len(f.job_contracts_collection) * p.ratio.ratio_of_employee_turnover)
            for _ in range(n):
                # 裁人
                wages_h_ask = {h_id:jb.wage for h_id, jb in f.job_contracts_collection.items()}
                # 选出工资最高的
                h_id = max(wages_h_ask, key=wages_h_ask.get)
                h = self.households_map[h_id]
                employed = h.job_contract.employed
                new_employed = employed[1:]
                new_employed.append(False)
                contract = {
                    'wage': h.job_contract.wage,
                    'dole': self.yearly_info[self.year].doles,
                    'household_id': h.household_id,
                    'employer': self.government.id_(),
                    'employed': new_employed
                    }
                new_contract = JobContract(contract)
                h.job_contract = new_contract
                del f.job_contracts_collection[h_id]
                self.government.job_contracts_collection[h_id] = new_contract
                # 招人
                all_h_id = [h_id for h_id, b in self.government.job_contracts_collection.items() if not b.employed[-1]]
                # 从失业人员里面选人
                new_h_id = random.choice(all_h_id)
                new_h = self.households_map[new_h_id]
                employed = new_h.job_contract.employed
                new_employed = employed[1:]
                new_employed.append(True)
                contract = {
                    'wage': new_h.job_contract.wage,
                    'dole': self.yearly_info[self.year].doles,
                    'household_id': new_h.household_id,
                    'employer': f.id_(),
                    'employed': new_employed
                }
                new_job_contraction = JobContract(contract)
                new_h.job_contract = new_job_contraction
                f.job_contracts_collection[new_h.household_id] = new_job_contraction
                # 政府的dole合同失效
                del self.government.job_contracts_collection[new_h.household_id]


    def credit_supply(self):
        """
        信贷供应，通过汇总公司的信贷需求，银行决定十分房贷，形成合同
        :return:
        """
        ep_args = {'c_firm': self.ep_item.c_firm_new_loan, 'k_firm': self.ep_item.k_firm_new_loan}
        for firms in [self.c_firms, self.k_firms]:
            for firm in firms:
                
                if len(firm.batch_of_loan_contracts.loan_contracts) > 0:
                    loan_bank = self.banks_map[firm.batch_of_loan_contracts.loan_contracts[0].bank_id]
                else:
                    # 如果没有合约可以参考，就找自己存款的银行贷款
                    loan_bank = self.banks_map[firm.deposits_contracts.bank_id]
                
                if firm.is_cap_firm:
                    loan_demand = firm.k_finance()
                else:
                    loan_demand = firm.c_finance()

                if loan_demand > 0:

                    deposit_bank = self.banks_map[firm.deposits_contracts.bank_id]

                    loan_contract = {
                        'amount': loan_demand,
                        'interest_rate': loan_bank.loan_interest_rate,
                        'firm_id': firm.firm_id,
                        'contract_year': self.year,
                        'left_year': p.para.duration_or_loan + 1,
                        'bank_id': loan_bank.bank_id
                    }
                    # 打钱 payer 付钱，其资产（存在央行的）减少， payee收钱，其资产（存款增加）,
                    # payee_bank收钱，其资产（保证金）增加，其负债（储蓄者的存款）也增加，净资产无变化
                    trade_from_reserve_to_deposit(
                        amount=loan_demand,
                        payer=loan_bank,
                        payee=firm,
                        payee_bank=deposit_bank,
                        item=ep_args[firm.identity],
                        transaction_flow_item=self.tf_item.delta_loans
                    )
                    # 银行创造贷款的时候，同时创造了一份存款，对于公司来说，贷款增加，存款增加，贷款创造的一瞬间同时创造了资产和负债
                    loan_contract_both = LoanContract(loan_contract)
                    firm.batch_of_loan_contracts.add_new_loan(loan_contract_both)

                    # 贷款银行的贷款合约要变化,公司一侧同步更新
                    firm_loan_bank_contracts = loan_bank.loans_contracts.setdefault(
                        firm.firm_id, LoanContractCollection([])
                    )
                    firm_loan_bank_contracts.add_new_loan(loan_contract_both)

        print("信贷供应完成")

    def households_observe_deposits_interests_rate(self):
        """
        居民部门观察存款利率市场各银行的报价
        :return:
        """
        price_collection = {}
        num_b = p.a.potential_partner_of_d_and_l_firm  # 确定报价数量
        all_ask_interest_rate = {b.bank_id: b.deposit_interest_rate for b in self.banks}
        for h in self.households:
            filter_banks = random.sample(self.banks, num_b)
            price_collection[h.household_id] = {b.bank_id: all_ask_interest_rate[b.bank_id] for b in filter_banks}
        return price_collection, all_ask_interest_rate

    def update_household_deposits_bank(self):
        """
        居民根据所见到的不同银行的报价，决定是否切换自己的存款银行
        """
        banks_bid_interest_rate_for_households, all_bids = self.households_observe_deposits_interests_rate()
        # 遍历 households
        for h in self.households:
            # 目前银行的利率
            old_bank_interest_rate = all_bids[h.deposits_contracts.bank_id]
            old_bank = self.banks_map[h.deposits_contracts.bank_id]
            other_bank_interest_rate = banks_bid_interest_rate_for_households[h.household_id]
            competitor_bank_id = max(other_bank_interest_rate, key=other_bank_interest_rate.get)  # 获取竞争者id
            competitor_interest_rate = other_bank_interest_rate[competitor_bank_id]
            if competitor_interest_rate > old_bank_interest_rate:
                if random.random() < p.a.probability_of_switch:
                    transfer_amount = h.deposits_contracts.amount
                    new_contract = {
                        'bank_id': competitor_bank_id,
                        'depositor': h.household_id,
                        'amount': transfer_amount,
                        'interest_rate': competitor_interest_rate
                    }
                    new_contract_assignment = DepositContract(new_contract)
                    h.deposits_contracts = new_contract_assignment
                    competitor_bank = self.banks_map[competitor_bank_id]
                    competitor_bank.deposits_contracts[h.household_id] = new_contract_assignment
                    del old_bank.deposits_contracts[h.household_id]  # 删除老存款合约
                    trade_from_reserve_to_reserve(
                        amount=transfer_amount, 
                        payer=old_bank, 
                        payee=competitor_bank,
                        item="-居民换银行存款", 
                        transaction_flow_item='存款搬家'
                        )
                    # print('done')

    def cal_unemployment_rate(self):
        employed = [h.job_contract.employed[-1] for h in self.households]
        rate = 1 - np.sum(employed) / len(self.households)
        return rate

    def net_work_from_c_to_k(self, item):
        df = pd.DataFrame(index=self.c_firms_map.keys(), columns=self.k_firms_map.keys())
        for c_firm in self.c_firms:
            for k_firm_id, contract in c_firm.capital_goods_contracts_collection.items():
                item_dic = {'state': 1, 'quantity':contract.quantity, 'price': contract.price}
                df.loc[c_firm.firm_id, k_firm_id] = item_dic[item]
        return df

    def net_work_from_k_to_c(self, item):
        df = pd.DataFrame(index=self.c_firms_map.keys(), columns=self.k_firms_map.keys())
        for k_firm in self.k_firms:
            for c_firm_id, contract in k_firm.capital_goods_contracts_collection.items():
                item_dic = {'state': 1, 'quantity': contract.quantity, 'price': contract.price}
                df.loc[c_firm_id, k_firm.firm_id] = item_dic[item]
        return df

    def net_work_from_h_to_c(self, item):
        df = pd.DataFrame(0,index=self.households_map.keys(), columns=self.c_firms_map.keys())
        for h in self.households:
            item_dic = {'state': 1, 'quantity': h.consumption_contract.quantity, 'price': h.consumption_contract.price}
            df.loc[h.household_id, h.consumption_contract.c_firm_id] = item_dic[item]
        return df
    
    def net_work_from_c_to_h(self, item):
        df = pd.DataFrame(0, index=self.households_map.keys(), columns=self.c_firms_map.keys())
        for c_firm in self.c_firms:
            for h_id, contract in c_firm.consumption_goods_contracts_collection.items():
                item_dic = {'state': 1, 'quantity': contract.quantity, 'price': contract.price}
                df.loc[h_id, c_firm.firm_id] = item_dic[item]
        return df

    def check_hc_net_work(self):
        # 遍历C，看每一个C的collection
        net_ch = self.net_work_from_c_to_h('state')
        # 遍历H，看每一个H的
        net_hc = self.net_work_from_h_to_c('state')
        comparison = net_ch != net_hc
        different_cells = np.where(comparison)
        for idx, col in zip(*different_cells):
            print(net_ch.iloc[idx, col], net_ch.index[idx], net_ch.columns[col]) 
            print(net_hc.iloc[idx, col], net_hc.index[idx], net_hc.columns[col])
        ch = net_ch.sum().sum()
        hc = net_hc.sum().sum()
        if ch != hc:
            print('c_supplier update error')
            print(ch, hc)
        return net_ch

    def c_firms_observe_k_firms_price(self):
        """
        c(消费)公司，每一个都会受到n个k（资本品供应商）的报价
        :return:
        """
        price_collection = {}
        num_k = p.a.potential_partner_of_c_and_k_firm  # 确定报价数量
        all_ask_price = {k_firm.firm_id: k_firm.price_and_recruit.price for k_firm in self.k_firms}
        for c_firm in self.c_firms:
            filter_k_firms = random.sample(self.k_firms, num_k)
            price_collection[c_firm.firm_id] = {k.firm_id: all_ask_price[k.firm_id] for k in filter_k_firms}
        return price_collection, all_ask_price

    def clearing_capital_contract(self, c_firm):
        c_firm_id = c_firm.firm_id
        k_firm_id_li = list(c_firm.capital_goods_contracts_collection.keys())
        c_firm.capital_goods_contracts_collection = {}
        for k_firm_id in k_firm_id_li:
            k_firm = self.k_firms_map[k_firm_id]
            del k_firm.capital_goods_contracts_collection[c_firm_id]
        answer_log = f"{c_firm.firm_id}清空合约"
        self.new_contract_log.append(answer_log)

    @staticmethod
    def create_a_new_capital_contract(c_firm, k_firm, price, quantity):
        if price == 0:
            print('wrong !!!')
        contract = {
            'c_firm_id': c_firm.firm_id,
            'k_firm_id': k_firm.firm_id,
            'price': price,
            'quantity': quantity
        }
        c_k_goods_contract = CapitalContract(contract)
        c_firm.capital_goods_contracts_collection[k_firm.firm_id] = c_k_goods_contract
        k_firm.capital_goods_contracts_collection[c_firm.firm_id] = c_k_goods_contract
        return c_k_goods_contract

    def c_firm_choose_a_k_supplier_after_clearing(self, c_firm, k_firms_bid_price_for_c, quantity):
        other_k_firm_ask_price_collection = k_firms_bid_price_for_c[c_firm.firm_id]
        competitor_firm_id = min(other_k_firm_ask_price_collection, key=other_k_firm_ask_price_collection.get)
        competitor_price = other_k_firm_ask_price_collection[competitor_firm_id]
        k_firm = self.k_firms_map[competitor_firm_id]
        c_k_goods_contract = self.create_a_new_capital_contract(c_firm, k_firm, competitor_price, quantity)
        answer_log = f"{c_firm.firm_id}新开合约：{c_k_goods_contract.__str__()}"
        self.new_contract_log.append(answer_log)

    def c_firm_change_a_k_supplier(self, c_firm, k_firms_bid_price_for_c, all_ask_price, investment_quantity, k_id):
        # 老的资本品合约
        capital_contract = c_firm.capital_goods_contracts_collection[k_id]
        # 老的资本品合约价格,不能用capital_contract.price 因为劳动力成本在变，要用all_ask_price里面的
        old_firm_ask_price = all_ask_price[capital_contract.k_firm_id]
        # C公司基于自身的视角看到的其他公司的报价
        other_k_firm_ask_price_collection = k_firms_bid_price_for_c[c_firm.firm_id]
        competitor_firm_id = min(other_k_firm_ask_price_collection, key=other_k_firm_ask_price_collection.get)
        # 竞争者价格，和竞争者id
        competitor_price = other_k_firm_ask_price_collection[competitor_firm_id]
        old_k_firm = self.k_firms_map[capital_contract.k_firm_id]
        # 竞争者价格和C公司目前供应商的报价的差值
        ptg = (old_firm_ask_price - competitor_price) / abs(np.mean([competitor_price, old_firm_ask_price]))
        # 如果竞争者报价低于老的供应商报价一定的阈值，则按照概率切换供应商
        probability_of_switch = p.a.probability_of_switch_of_c_k_firm
        # 竞争者公司实体
        k_firm = self.k_firms_map[competitor_firm_id]
        # 如果上期c公司有不为零的，按照之前签约的比例
        c_last_period_quantity = np.sum([c.quantity for _, c in c_firm.capital_goods_contracts_collection.items()])
        ratio = capital_contract.quantity / c_last_period_quantity
        quantity = investment_quantity * ratio
        # 如果价格差异超过了阈值的同时，概率发生了
        if ptg > p.a.diff_of_ptge_between_price_triggle_switch_ck and random.random() <= probability_of_switch:
            # 形成新的合同,先删老的，再添加新的，有可能old_k_firm和k_firm是同一个公司
            if c_firm.firm_id in old_k_firm.capital_goods_contracts_collection.keys():
                del old_k_firm.capital_goods_contracts_collection[c_firm.firm_id]
            if old_k_firm.firm_id in c_firm.capital_goods_contracts_collection.keys():
                del c_firm.capital_goods_contracts_collection[old_k_firm.firm_id]
            # ptg价差比例超过阈值后，有probability_of_switch的概率切换供应商
            c_k_goods_contract = self.create_a_new_capital_contract(c_firm, k_firm, competitor_price, quantity)
            answer_log = f"{c_firm.firm_id}切换供应商合约：{c_k_goods_contract.__str__()}"
        else:
            # 如果不切换供应商，那么也要更新合同，因为价格，需求量和上一期也不一样
            c_k_goods_contract = self.create_a_new_capital_contract(c_firm, old_k_firm, old_firm_ask_price, quantity)
            answer_log = f"{c_firm.firm_id}在老供应商基础上更新合约：{c_k_goods_contract.__str__()}"
        self.new_contract_log.append(answer_log)

    def update_k_supplier(self):
        # 资本品公司重新报价，形成一个字典k_firms_bid_price_for_c
        k_firms_bid_price_for_c, all_ask_price = self.c_firms_observe_k_firms_price()  # 这个函数一个循环只能调用一次
        start_contract_log_number = len(self.new_contract_log)
        for c_firm in self.c_firms:
            # 上一期C的需求
            c_last_period_quantity = np.sum([c.quantity for _, c in c_firm.capital_goods_contracts_collection.items()])
            investment_quantity = c_firm.investment() # 本次C的需求
            capital_goods_contract_keys = list(c_firm.capital_goods_contracts_collection.keys())
            if investment_quantity == 0: #
                # 投资额为零，删除所有合约
                self.clearing_capital_contract(c_firm)
            else:
                if c_last_period_quantity == 0:
                    self.c_firm_choose_a_k_supplier_after_clearing(
                        c_firm, 
                        k_firms_bid_price_for_c, 
                        investment_quantity
                        )
                else:
                    for k_id in capital_goods_contract_keys:
                        self.c_firm_change_a_k_supplier(
                            c_firm, 
                            k_firms_bid_price_for_c, 
                            all_ask_price,
                            investment_quantity, k_id
                            )


        if len(self.new_contract_log) > start_contract_log_number:
            print(self.new_contract_log[-1])

    def households_observe_consumption_price(self):
        """
        c(消费)公司，每一个都会受到n个k（资本品供应商）的报价
        :return:
        """
        price_collection = {}
        num_c = p.a.potential_partner_of_c_and_k_firm  # 确定报价数量
        all_ask_price = {f.firm_id: f.price_and_recruit.price for f in self.c_firms}
        for h in self.households:
            filter_c_firms = random.sample(self.c_firms, num_c)
            price_collection[h.household_id] = {f.firm_id: all_ask_price[f.firm_id] for f in filter_c_firms}
        return price_collection, all_ask_price

    def update_c_supplier(self):
        """
        c公司更新供应商,c公司依次对消费品报价，居民h能够看到部分报价信息，如果竞争者最低报价小于当前的供应商报价，
        那么有一定概率切换供应商
        """
        c_firms_bid_price_for_h, all_c_firm_bid_price = self.households_observe_consumption_price()  # 这个函数一个循环只能调用一次

        for h in self.households:
            # 对于h居民来说，其合同对方C公司报价
            old_c_firm_ask_price = all_c_firm_bid_price[h.consumption_contract.c_firm_id]
            old_c_firm = self.c_firms_map[h.consumption_contract.c_firm_id]
            # 获取其他c公司对当前用户的报价
            other_c_firm_ask_price_collection = c_firms_bid_price_for_h[h.household_id]
            # 看看其他c公司中最低报价的供应商是谁
            competitor_firm_id = min(other_c_firm_ask_price_collection, key=other_c_firm_ask_price_collection.get)
            competitor_price = other_c_firm_ask_price_collection[competitor_firm_id]
            # 计算竞争者最低价格超出报价的比例，
            ptg = (old_c_firm_ask_price - competitor_price) / abs(np.mean([competitor_price, old_c_firm_ask_price]))
            p_of_switch = p.a.probability_of_switch_of_c_k_firm
            # 如果竞争者最低报价小于当前的供应商报价，那么有一定概率切换供应商
            if h.job_contract.employed[-1]:
                income = h.job_contract.wage
            else:
                income = h.job_contract.dole
            
            if ptg > p.a.diff_of_ptge_between_price_triggle_switch_ck and random.random() <= p_of_switch:
                del old_c_firm.consumption_goods_contracts_collection[h.household_id]
                c_firm = self.c_firms_map[competitor_firm_id]
                a1, a2, max_q, q = h.consume(competitor_price)
                contract = {
                    'household_id': h.household_id,
                    'c_firm_id': c_firm.firm_id,
                    'price': competitor_price,
                    'quantity': q,
                    'con_deposit':h.deposits_contracts.amount,
                    'con_income': income,
                    'a1':a1,
                    'a2':a2,
                    'max_q':max_q,
                    }
                # 创建合同实例，h和c分别更新合同
                new_consumption_contract = ConsumptionContract(contract)
                h.consumption_contract = new_consumption_contract
                c_firm.consumption_goods_contracts_collection[h.household_id] = new_consumption_contract
                # self.check_hc_net_work()
            else:
                # 不切换供应商也要更新合同,因为消费量变了
                a1, a2, max_q, q = h.consume(old_c_firm_ask_price)
                contract = {
                    'household_id': h.household_id,
                    'c_firm_id': old_c_firm.firm_id,
                    'price': old_c_firm_ask_price,
                    'quantity': q,
                    'con_deposit':h.deposits_contracts.amount,
                    'con_income': income,
                    'a1':a1,
                    'a2':a2,
                    'max_q':max_q,
                    }
                new_consumption_contract = ConsumptionContract(contract)
                h.consumption_contract = new_consumption_contract
                old_c_firm.consumption_goods_contracts_collection[h.household_id] = new_consumption_contract


    def describe_k_order(self):
        v_quantity, v_amount = {}, {}
        for k in self.k_firms:
            q = np.sum([c.quantity for _,c in k.capital_goods_contracts_collection.items()])
            a = np.sum([c.quantity * c.price for _,c in k.capital_goods_contracts_collection.items()])
            v_quantity[k.id_()] = q
            v_amount[k.id_()] = a
        return v_quantity, v_amount